package junior.db.dao.mybatis.interceptor;

import com.google.common.collect.Lists;
import junior.db.annotation.TypeEntity;
import junior.db.dao.base.AbstractFilter;
import junior.db.dao.base.Filter;
import junior.db.dao.base.JoinQuery;
import junior.db.dao.base.MybatisHelper;
import junior.db.dao.base.SubQuery;
import junior.db.dao.base.UnionQuery;
import junior.db.dao.base.IEntityInject;
import junior.db.dao.base.IEntityWrapper;
import junior.db.dao.base.IExecuteAware;
import junior.db.dao.base.INamespaceAware;
import junior.db.dao.mybatis.base.IBaseMapper;
import junior.db.dao.mybatis.type.AssociationType;
import junior.db.dao.mybatis.type.ResultType;
import junior.db.dao.mybatis.type.TypeValue;
import junior.db.dao.query.AbstractQuery;
import junior.db.dao.update.AbstractUpdateOption;
import junior.db.query.QueryHelper;
import junior.util.reflect.ReflectUtils;
import junior.util.value.ValueUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        ), @Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class}
)
})
public class BaseMapperInterceptor implements Interceptor {
    private String namespace;
    private boolean allowUpdateWithoutFilter;
    private boolean handleTypeValue;
    
    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }
    
    public void setHandleTypeValue(boolean handleTypeValue) {
        this.handleTypeValue = handleTypeValue;
    }
    
    public void setAllowUpdateWithoutFilter(boolean allowUpdateWithoutFilter) {
        this.allowUpdateWithoutFilter = allowUpdateWithoutFilter;
    }
    
    public BaseMapperInterceptor() {}
    
    public BaseMapperInterceptor(boolean handleTypeValue) {
        this.handleTypeValue = handleTypeValue;
    }
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof Executor) {
            return doInterceptExecutor(invocation);
        }
        
        return invocation.proceed();
    }
    
    private Object doInterceptExecutor(Invocation invocation) throws Throwable {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        
        boolean isQuery = "query".equals(invocation.getMethod().getName());
        boolean isUpdate = "update".equals(invocation.getMethod().getName());
        
        Object mapperParam = args[1];
        Object arg1 = mapperParam;
        if (arg1 == null) {
            return invocation.proceed();
        }
        ResultType<?> resultType = null;
        Class<?> entityClass = null;
        if (arg1 instanceof Map) {
            Map paramMap = (Map) arg1;
            arg1 = paramMap.values().stream().filter(param -> param instanceof IEntityWrapper).findFirst().orElse(null);
            if (arg1 == null) {
                return invocation.proceed();
            }
            if (requireSetResultType(ms) && paramMap.containsKey("resultType")) {
                resultType = (ResultType<?>) paramMap.get("resultType");
            }
        }
        
        if (namespace != null && arg1 instanceof INamespaceAware) {
            ((INamespaceAware) arg1).setNamespace(namespace);
        }
        
        if (arg1 instanceof IEntityWrapper && arg1 instanceof IEntityInject) {
            if (((IEntityWrapper) arg1).getEntityClass() == null) {
                int lastIndex = ms.getId().lastIndexOf(".");
                if (lastIndex != -1) {
                    String mapperClassName = ms.getId().substring(0, lastIndex);
                    Class<?> mapperClass = ClassUtils.forName(mapperClassName, null);
                    if (IBaseMapper.class.isAssignableFrom(mapperClass)) {
                        entityClass = MybatisHelper.getMapperEntityClass(mapperClass);
                        ((IEntityInject) arg1).setEntityClass(entityClass);
                    }
                }
            }
        }
        
        if (isQuery) {
            flushCacheRequired(ms);
            
            Pair<Boolean, Object> pair = doQuery(invocation, ms, arg1, entityClass, resultType);
            if (pair.getLeft()) {
                return pair.getRight();
            }
            logSql(ms, mapperParam);
        } else if (isUpdate && !allowUpdateWithoutFilter) {
            if (arg1 instanceof AbstractUpdateOption && !((AbstractUpdateOption) arg1).hasAnyFilter()) {
                throw new SQLException("Update without filters is not allowed. You can set value of BaseMapperInterceptor.allowUpdateWithoutFilter to true to allow without filters on update");
            }
        }
        
        Object replaced = MybatisExtHelper.doExt(arg1);
        if (replaced != null) {
            arg1 = replaced;
        }
        try {
            MybatisConfigurationHolder.setConfiguration(ms.getConfiguration());
            if (arg1 instanceof IExecuteAware) {
                ((IExecuteAware) arg1).beforeExecute();
            }
            return invocation.proceed();
        } finally {
            MybatisConfigurationHolder.clear();
            if (arg1 instanceof IExecuteAware) {
                ((IExecuteAware) arg1).afterExecute();
            }
        }
    }
    
    private static Field flushCacheRequired;
    private void flushCacheRequired(MappedStatement ms) {
        try {
            if (flushCacheRequired == null) {
                flushCacheRequired = MappedStatement.class.getDeclaredField("flushCacheRequired");
                flushCacheRequired.setAccessible(true);
            }
            flushCacheRequired.set(ms, true);
        } catch (Exception ignore) {}
    }
    
    private Pair<Boolean, Object> doQuery(Invocation invocation, MappedStatement ms, Object arg1, Class<?> entityClass, ResultType<?> resultType) {
        final Object[] args = invocation.getArgs();
        
        boolean rebuildResultMap = false;
        Boolean autoMapping = null;
        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        List<ResultMapping> resultMappings = Lists.newArrayList();
        
        Class<?> resultClass = MybatisHelper.getResultType(ms);
        
        if (supports(ms)) {
            if (arg1 instanceof AbstractQuery) {
                AbstractQuery eq = (AbstractQuery) arg1;
                if (resultType == null) {
                    resultType = eq.getResultType();
                }
                if (entityClass == null && eq.getEntityClass() != null) {
                    entityClass = eq.getEntityClass();
                }
                
                if (resultType != null && !resultType.getType().equals(resultClass)) {
                    rebuildResultMap = true;
                    resultClass = resultType.getType();
                }
                if (resultType != null && resultType.getAssociationTypes() != null && !resultType.getAssociationTypes().isEmpty()) {
                    handleAssociationTypes(ms, eq, resultClass, resultMappings);
                    rebuildResultMap = true;
                    autoMapping = Boolean.TRUE;
                }
    
                handleJoinParamsOfJoinParam(eq);
                
                if (handleTypeValue) {
                    if (eq.getJoinList() != null) {
                        List<JoinQuery> joinQueryList = eq.getJoinList();
                        for (JoinQuery param : joinQueryList) {
                            handleFilterTypeValue(param.getJoinQuery(), null);
                            handleAllSubQueryRecursively(param.getJoinQuery());
                            if (handleResultTypeValue(ms, param.getJoinQuery(), resultMappings, true, null)) {
                                rebuildResultMap = true;
                            }
                        }
                    }
    
                    handleAllSubQueryRecursively(eq);
                }
            }
        }
    
        if (arg1 instanceof AbstractQuery) {
            AbstractQuery q = (AbstractQuery) arg1;
            
            if (handleTypeValue && supportsHandleResultType(ms) && handleResultTypeValue(ms, q, resultMappings, false, entityClass)) {
                rebuildResultMap = true;
            }
        }
    
        if (handleTypeValue && arg1 instanceof AbstractFilter<?>) {
            handleFilterTypeValue((AbstractFilter<?>) arg1, entityClass);
        }
    
        if (rebuildResultMap) {
            ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), resultClass, resultMappings, autoMapping).build();
            resultMaps.add(resultMap);
            args[0] = MybatisHelper.newMappedStatement(ms, resultClass, resultMaps);
        }
    
        return Pair.of(Boolean.FALSE, null);
    }
    
    private void handleAssociationTypes(MappedStatement ms, AbstractQuery<?> eq, Class<?> resultType, List<ResultMapping> resultMappings) {
        for (AssociationType type : eq.getResultType().getAssociationTypes()) {
            List<Pair<String, Class<?>>> fieldAndTypes = type.getColumnMapping() != null ? getFieldAndTypes(type.getType(), type.getColumnMapping()) : null;
            List<ResultMapping> associateResultMappings = buildResultMappings(ms, eq, fieldAndTypes, type.getColumnMapping());
            String resultMapId = type.generateId(ms.getId(), resultType.getSimpleName());
            ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), resultMapId, type.getType(), associateResultMappings, true).build();
            if (!ms.getConfiguration().hasResultMap(resultMapId)) {
                ms.getConfiguration().addResultMap(resultMap);
            }
            resultMappings.add(new ResultMapping.Builder(ms.getConfiguration() , type.getField())
                    .javaType(ValueUtils.ifNull(type.getCollectionType(), type.getType())).nestedResultMapId(resultMap.getId()).build());
        }
    }
    
    private List<ResultMapping> buildResultMappings(MappedStatement ms, AbstractQuery eq, List<Pair<String, Class<?>>> fieldAndTypes, Map<String, String> columnMapping) {
        if (fieldAndTypes != null) {
            List<ResultMapping> resultMappings = Lists.newArrayListWithExpectedSize(fieldAndTypes.size());
            for (Pair<String, Class<?>> pair: fieldAndTypes) {
                String column = columnMapping != null ? columnMapping.get(pair.getKey()) : null;
                if (column == null) {
                    column = eq.isSelectAsAlias() ? pair.getLeft() : MybatisHelper.field2Column(namespace, pair.getRight(), pair.getLeft());
                }
                resultMappings.add(new ResultMapping.Builder(ms.getConfiguration(), pair.getLeft(), column, pair.getRight()).build());
            }
            return resultMappings;
        }
        return Collections.emptyList();
    }
    
    private void handleAllSubQueryRecursively(AbstractQuery eq) {
        if (eq.getSubQueryList() != null) {
            List<SubQuery> subQueryList = eq.getSubQueryList();
            for (SubQuery param : subQueryList) {
                handleFilterTypeValue(param.getQuery(), null);
                handleAllSubQueryRecursively(param.getQuery());
            }
        }
        if (eq.getUnionQuery() != null) {
            for (UnionQuery.UnionQueryParamItem item : eq.getUnionQuery().getItems()) {
                handleAllSubQueryRecursively(item.getParam());
            }
        }
    }
    
    // 处理join查询的join查询，目前只有主查询的join才会生效
    private void handleJoinParamsOfJoinParam(AbstractQuery eq) {
        if (eq.getJoinList() != null) {
            List<JoinQuery> container = new ArrayList<>();
            List<JoinQuery> joinQueryList = eq.getJoinList();
            for (JoinQuery param : joinQueryList) {
                doHandleJoinParamsOfJoinParam(param.getJoinQuery(), container);
            }
            if (!container.isEmpty()) {
                eq.getJoinList().addAll(container);
            }
        }
    
        if (eq.getSubQueryList() != null) {
            List<SubQuery> subQueryList = eq.getSubQueryList();
            for (SubQuery sub : subQueryList) {
                if (sub.getQuery().getJoinList() != null) {
                    for (JoinQuery param : sub.getQuery().getJoinList()) {
                        doHandleJoinParamsOfJoinParam(param.getJoinQuery(), eq.getJoinList());
                    }
                }
            }
        }
        if (eq.getUnionQuery() != null) {
            for (UnionQuery.UnionQueryParamItem item : eq.getUnionQuery().getItems()) {
                handleJoinParamsOfJoinParam(item.getParam());
            }
        }
    }
    
    private void doHandleJoinParamsOfJoinParam(AbstractQuery joinParam, List<JoinQuery> container) {
        if (joinParam.getJoinList() != null) {
            List<JoinQuery> joinQueryList = joinParam.getJoinList();
            for (JoinQuery param : joinQueryList) {
                container.add(param);
                doHandleJoinParamsOfJoinParam(param.getJoinQuery(), container);
            }
        }
    }
    
    private boolean handleResultTypeValue(MappedStatement ms, AbstractQuery q, List<ResultMapping> resultMappings,
                                          boolean requireExplicitSelectFields, Class<?> candidateEntityClass) {
        if (! doHandleResultTypeValue(ms, q, resultMappings, requireExplicitSelectFields, q.getEntityClass()) && candidateEntityClass != null) {
            return doHandleResultTypeValue(ms, q, resultMappings, requireExplicitSelectFields, candidateEntityClass);
        }
        return true;
    }
    
    private boolean doHandleResultTypeValue(MappedStatement ms, AbstractQuery q, List<ResultMapping> resultMappings,
                                            boolean requireExplicitSelectFields, Class<?> entityClass) {
        boolean rebuildResultMap = false;
        if (entityClass != null) {
            String[] selectFields = q.getSelectFields();
            if (selectFields == null || selectFields.length < 1) {
                if (requireExplicitSelectFields) {
                    return false;
                }
                selectFields = QueryHelper.getDBFieldNames(entityClass, null);
            }
            if (entityClass.getAnnotation(TypeEntity.class) != null) {
                for (String field : selectFields) {
                    String type = MybatisHelper.getFieldType(entityClass, field);
                    if (type != null) {
                        Class<Object> aliasType = ms.getConfiguration().getTypeAliasRegistry().resolveAlias(type);
                        TypeHandler<Object> handler = ms.getConfiguration().getTypeHandlerRegistry().getTypeHandler(aliasType);
                        ResultMapping resultMapping = new ResultMapping.Builder(ms.getConfiguration(), field,
                                q.isSelectAsAlias() ? field : MybatisHelper.field2Column(namespace, field, (p) -> entityClass), handler).build();
                        resultMappings.add(resultMapping);
                
                        if (!rebuildResultMap) {
                            rebuildResultMap = true;
                        }
                    }
                }
            }
        }
        return rebuildResultMap;
    }
    
    private void handleFilterTypeValue(AbstractFilter<?> param, Class<?> candidateEntityClass) {
        if (!doHandleFilterTypeValue(param, param.getEntityClass()) && candidateEntityClass != null) {
            doHandleFilterTypeValue(param, candidateEntityClass);
        }
    }
    
    private boolean doHandleFilterTypeValue(AbstractFilter<?> param, Class<?> entityClass) {
        boolean handled = false;
        if (entityClass != null && param.hasAnyFilter() &&
                entityClass.getAnnotation(TypeEntity.class) != null) {
            for (String field : QueryHelper.getDBFieldNames(entityClass, null)) {
                if (param.hasFilter(field)) {
                    String type = MybatisHelper.getFieldType(entityClass, field);
                    if (type != null) {
                        List<Filter> filters = param.getFiltersByField(field);
                        for (Filter filter : filters) {
                            if (filter.isLogic()) {
                                continue;
                            }
                            handleTypeValueForFilterRight(filter, type);
                        }
                        if (!handled) {
                            handled = true;
                        }
                    }
                }
            }
        }
        return handled;
    }
    
    // 处理Filter的TypeValue
    private void handleTypeValueForFilterRight(Filter filter, String type) {
        Object value = filter.getRight();
        if (value == null) {
            return;
        }
        if (value instanceof Collection) {
            filter.setRight(((Collection<?>) value).toArray());
        }
        
        if (value.getClass().isArray()) {
            for (int i=0; i< Array.getLength(value); i++) {
                Object obj = Array.get(value, i);
                Object newValue = tryWrapperToTypeValue(obj, type);
                if (newValue != obj) {
                    Array.set(value, i, newValue);
                }
            }
        }
    
        Object newValue = tryWrapperToTypeValue(value, type);
        if (newValue != filter.getRight()) {
            filter.setRight(newValue);
        }
    }
    
    private Object tryWrapperToTypeValue(Object value, String type) {
        if (value != null && !(value instanceof TypeValue)) {
            return new TypeValue(value, type, value.getClass());
        }
        return value;
    }
    
    private List<Pair<String, Class<?>>> getFieldAndTypes(Class<?> type, Map<String, String> columnMapping) {
        PropertyDescriptor[] propertyDescriptors = ReflectUtils.getPropertyDescriptors4IncludedNames(type, columnMapping.keySet().toArray(new String[0]));
        List<Pair<String, Class<?>>> result = Lists.newArrayListWithExpectedSize(propertyDescriptors.length);
        for (PropertyDescriptor pd : propertyDescriptors) {
            result.add(Pair.of(pd.getName(), pd.getPropertyType()));
        }
        return result;
    }
    
    protected boolean supports(MappedStatement ms) {
        return ms.getId().endsWith("list") || ms.getId().endsWith("get") ||
                ms.getId().endsWith("listForObject") || ms.getId().endsWith("getForObject") ||
                ms.getId().endsWith("listForMap") || ms.getId().endsWith("getForMap");
    }
    
    protected boolean requireSetResultType(MappedStatement ms) {
        return ms.getId().endsWith("listForObject") || ms.getId().endsWith("getForObject");
    }
    
    protected boolean supportsHandleResultType(MappedStatement ms) {
        return !ms.getId().endsWith("count");
    }
    
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    
    @Override
    public void setProperties(Properties properties) {
        namespace = properties.getProperty("namespace");
        String handleTypeValueStr = properties.getProperty("handleTypeValue", "false");
        handleTypeValue = Boolean.parseBoolean(handleTypeValueStr);
    }
    
    private void logSql(MappedStatement ms, Object arg1) {
        String id = ms.getId();
        int pointIndex = id.lastIndexOf(".");
        String mapperClass = pointIndex != -1 ? id.substring(0, pointIndex) : id;
        Logger logger = LoggerFactory.getLogger(mapperClass);
        if (logger.isDebugEnabled()) {
            BoundSql boundSql = ms.getBoundSql(arg1);
            List<String> params = MybatisParamUtils.getAllParams(ms, boundSql);
            String sql = boundSql.getSql();
            StringBuilder buff = new StringBuilder();
            boolean isInDoubleQuote = false;
            boolean isInQuote = false;
            int qIndex = 0;
            for (char c : sql.toCharArray()) {
                if (c == '"') {
                    isInDoubleQuote = !isInDoubleQuote;
                }
                if (c == '\'') {
                    isInQuote = !isInQuote;
                }
                if (c == '?' && !isInDoubleQuote && !isInQuote) {
                    buff.append(params.get(qIndex++));
                } else {
                    buff.append(c);
                }
            }
            logger.debug("==> SQL: {}", buff);
        }
    }
    
    private static class MybatisParamUtils {
        private static Logger logger = LoggerFactory.getLogger(MybatisParamUtils.class);
        private static Map<Type, Integer> type2JdbcType = new HashMap();
        
        static {
            type2JdbcType.put(String.class, JdbcType.VARCHAR.TYPE_CODE);
            type2JdbcType.put(Date.class, JdbcType.DATE.TYPE_CODE);
            type2JdbcType.put(Time.class, JdbcType.TIME.TYPE_CODE);
            type2JdbcType.put(Timestamp.class, JdbcType.TIMESTAMP.TYPE_CODE);
            type2JdbcType.put(Date.class, JdbcType.TIMESTAMP.TYPE_CODE);
            type2JdbcType.put(byte[].class, JdbcType.BLOB.TYPE_CODE);
            type2JdbcType.put(Byte[].class, JdbcType.BLOB.TYPE_CODE);
        }
        
        public static List<String> getAllParams(MappedStatement mappedStatement, BoundSql boundSql) {
            ParamResolveHelper helper = new ParamResolveHelper();
            List<String> params = new ArrayList();
            Object parameterObject = boundSql.getParameterObject();
            TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
            Configuration configuration = mappedStatement.getConfiguration();
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            if (parameterMappings != null) {
                for(int i = 0; i < parameterMappings.size(); ++i) {
                    ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
                    if (parameterMapping.getMode() != ParameterMode.OUT) {
                        String propertyName = parameterMapping.getProperty();
                        Object value;
                        if (boundSql.hasAdditionalParameter(propertyName)) {
                            value = boundSql.getAdditionalParameter(propertyName);
                        } else if (parameterObject == null) {
                            value = null;
                        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                            value = parameterObject;
                        } else {
                            MetaObject metaObject = configuration.newMetaObject(parameterObject);
                            value = metaObject.getValue(propertyName);
                        }
                        
                        JdbcType jdbcType = parameterMapping.getJdbcType();
                        if (value == null && jdbcType == null) {
                            jdbcType = configuration.getJdbcTypeForNull();
                        }
                        
                        if (jdbcType == null) {
                            jdbcType = lookupJdbcType(typeHandlerRegistry, value);
                        }
                        
                        params.add(dealJdbcType(jdbcType, value, helper));
                    }
                }
            }
            
            return params;
        }
        
        public static JdbcType lookupJdbcType(TypeHandlerRegistry typeHandlerRegistry, Object value) {
            if (value == null) {
                return null;
            }
            Integer jdbcType = getJdbcType(value);
            if (jdbcType != null) {
                return JdbcType.forCode(jdbcType);
            } else {
                return value instanceof InputStream ? JdbcType.BLOB : null;
            }
        }
        
        public static Integer getJdbcType(Object param) {
            if (param == null) {
                return null;
            }
            Integer jdbcType = (Integer)type2JdbcType.get(param.getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("jdbcType: {}, param: {}, paramClass: {}", jdbcType, param, param.getClass());
            }
            if (jdbcType != null) {
                return jdbcType;
            } else {
                return param instanceof InputStream ? JdbcType.BLOB.TYPE_CODE : null;
            }
        }
        
        public static String dealJdbcType(JdbcType jdbcType, Object value, ParamResolveHelper helper) {
            if (jdbcType == null) {
                jdbcType = JdbcType.INTEGER;
            }
            
            return dealJdbcType(jdbcType.TYPE_CODE, value, helper);
        }
        
        public static String dealJdbcType(Integer jdbcType, Object value, ParamResolveHelper helper) {
            if (value == null) {
                return "null";
            } else if (jdbcType == null) {
                return String.valueOf(value);
            } else {
                String result = "";
                switch(jdbcType) {
                    case -4:
                    case -3:
                    case -2:
                        result = "null";
                        break;
                    case -1:
                    case 1:
                    case 12:
                        result = ParamResolveHelper.addQuote(String.valueOf(value));
                        break;
                    case 91:
                        result = helper.formatDate(value);
                        break;
                    case 92:
                        result = helper.formatTime(value);
                        break;
                    case 93:
                        result = helper.formatDateTime(value);
                        break;
                    case 2004:
                    case 2005:
                    case 2011:
                        result = "null";
                        break;
                    default:
                        result = String.valueOf(value);
                }
                
                return result;
            }
        }
        
        public static class ParamResolveHelper {
            private SimpleDateFormat dateFormat = null;
            private SimpleDateFormat timeFormat = null;
            private SimpleDateFormat dateTimeFormat = null;
            
            public ParamResolveHelper() {
            }
            
            public String formatDate(Object value) {
                return value instanceof Date ? this.formatDate((Date)value) : addQuote(String.valueOf(value));
            }
            
            public String formatDate(Date date) {
                if (this.dateFormat == null) {
                    this.dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                }
                
                return addQuote(this.dateFormat.format(date));
            }
            
            public String formatTime(Object value) {
                return value instanceof Date ? this.formatTime((Date)value) : addQuote(String.valueOf(value));
            }
            
            public String formatTime(Date date) {
                if (this.timeFormat == null) {
                    this.timeFormat = new SimpleDateFormat("HH:mm:ss");
                }
                
                return addQuote(this.timeFormat.format(date));
            }
            
            public String formatDateTime(Object value) {
                return value instanceof Date ? this.formatDateTime((Date)value) : addQuote(String.valueOf(value));
            }
            
            public String formatDateTime(Date date) {
                if (this.dateTimeFormat == null) {
                    this.dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                }
                
                return addQuote(this.dateTimeFormat.format(date));
            }
            
            public static String addQuote(String s) {
                return "'" + s + "'";
            }
            
        }
    }
}
